﻿using SFML.Window;

namespace Transformable_Engine_v2.Engine.Transformable_Math.Geometry
{
    /// <summary>
    /// Class representing a Vector2
    /// </summary>
    public class TE_Vector2
    {
        /// <summary>
        /// Vector2 with minimal value
        /// </summary>
        public static TE_Vector2 MinValue
        {
            get 
            { 
                return new TE_Vector2(float.MinValue, float.MinValue);
            }
        }

        /// <summary>
        /// Predefined vector2 (0, 0)
        /// </summary>
        public static TE_Vector2 Zero
        {
            get { return new TE_Vector2(); }
        }

        /// <summary>
        /// Predefined vector2 (1, 1)
        /// </summary>
        public static TE_Vector2 One
        {
            get { return new TE_Vector2(1, 1); }
        }

        /// <summary>
        /// Predefined vector2 (1, 0)
        /// </summary>
        public static TE_Vector2 UnitX
        {
            get { return new TE_Vector2(1, 0); }
        }

        /// <summary>
        /// Predefined vector2 (0, 1)
        /// </summary>
        public static TE_Vector2 UnitY
        {
            get { return new TE_Vector2(0, 1); }
        }

        /// <summary>
        /// Squared value of the length of the vector2
        /// </summary>
        public double LengthSquared
        {
            get
            {
                return System.Math.Pow(X, 2) + System.Math.Pow(Y, 2);
            }
        }

        /// <summary>
        /// Length of the vector2
        /// </summary>
        public double Length
        {
            get
            {
                return System.Math.Sqrt(LengthSquared);
            }
        }

        /// <summary>
        /// Angle of the Vector2
        /// </summary>
        public double Angle
        {
            get
            {
                return System.Math.Atan2(Y, X);
            }
        }

        /// <summary>
        /// X component of the Vector2
        /// </summary>
        public float X { get; set; }

        /// <summary>
        /// Y component of the Vector2
        /// </summary>
        public float Y { get; set; }

        public TE_Vector2 FlipHorizontally
        {
            get { return new TE_Vector2(-X, Y); }
        }

        public TE_Vector2 FlipVertically
        {
            get { return new TE_Vector2(-Y, X); }
        }

        /// <summary>
        /// Creates a new instance of the Vector2 class, 
        /// Value = [0, 0]
        /// </summary>
        public TE_Vector2()
        {
            X = 0;
            Y = 0;
        }

        /// <summary>
        /// Creates a new instance of the Vector2 class
        /// </summary>
        /// <param name="x">X component</param>
        /// <param name="y">Y component</param>
        public TE_Vector2(float x, float y)
        {
            X = x;
            Y = y;
        }

        /// <summary>
        /// Creates a new instance of the Vector2 class
        /// </summary>
        /// <param name="angle">Angle of the Vector2</param>
        /// <param name="length">Length of the Vector2</param>
        public TE_Vector2(double angle, float length = 1)
        {
            X = (float) (System.Math.Cos(angle)*length);
            Y = (float) (System.Math.Sin(angle)*length);
        }

        /// <summary>
        /// Creates a new instance of the Vector2 class
        /// </summary>
        /// <param name="vector2">Vector2 to copy</param>
        public TE_Vector2(TE_Vector2 vector2)
            : this(vector2.X, vector2.Y)
        {
        }

        /// <summary>
        /// Multiplies a Vector2 by the grid size
        /// </summary>
        /// <returns></returns>
        public TE_Vector2 ToGridbound()
        {
            if(Game.Options == null)
                return Zero;

            return this * Game.Options.GridSize;
        }

        public TE_Vector2 AsGridBound()
        {
            return this / Game.Options.GridSize;
        }

        /// <summary>
        /// Allow conversion to SFML native type
        /// </summary>
        /// <returns>SFML Videomode</returns>
        public SFML.Window.VideoMode ToVideoMode()
        {
            return new SFML.Window.VideoMode((uint) X, (uint) Y);
        }

        /// <summary>
        /// Allow conversion to SFML native type
        /// </summary>
        /// <returns>SFML Vector2f</returns>
        public SFML.Window.Vector2f ToVector2f()
        {
            return new SFML.Window.Vector2f(X, Y);
        }

        /// <summary>
        /// Operator overload to allow for multiplication with a float
        /// </summary>
        /// <param name="vector2">Vector2 to multiply</param>
        /// <param name="multiplicant">Amount to multiply</param>
        /// <returns>Resulting vector2</returns>
        public static TE_Vector2 operator *(TE_Vector2 vector2, double multiplicant)
        {
            return new TE_Vector2((float) (vector2.X * multiplicant), (float) (vector2.Y * multiplicant));
        }

        public static TE_Vector2 operator /(TE_Vector2 vector2, double multiplicant)
        {
            return new TE_Vector2((float)(vector2.X / multiplicant), (float)(vector2.Y / multiplicant));
        }

        /// <summary>
        /// Operator overload to allow for multiplication with a float
        /// </summary>
        /// <param name="vector2">Vector2 to multiply</param>
        /// <param name="multiplicant">Amount to multiply</param>
        /// <returns>Resulting vector2</returns>
        public static TE_Vector2 operator *(double multiplicant, TE_Vector2 vector2)
        {
            return new TE_Vector2((float) (vector2.X * multiplicant), (float) (vector2.Y * multiplicant));
        }

        /// <summary>
        /// Operator overload to allow for addition of vectors
        /// </summary>
        /// <param name="vector1">Vector2 to add</param>
        /// <param name="vector2">Vector2 to add</param>
        /// <returns>Resulting vector2 (x1 + x2, y1 + y2)</returns>
        public static TE_Vector2 operator +(TE_Vector2 vector1, TE_Vector2 vector2)
        {
            return new TE_Vector2(vector1.X + vector2.X, vector1.Y + vector2.Y);
        }

        /// <summary>
        /// Operator overload to allow for substraction of vectors
        /// </summary>
        /// <param name="vector1">Vector2 to add</param>
        /// <param name="vector2">Vector2 to add</param>
        /// <returns>Resulting vector2 (x1 - x2, y1 - y2)</returns>
        public static TE_Vector2 operator -(TE_Vector2 vector1, TE_Vector2 vector2)
        {
            return new TE_Vector2(vector1.X - vector2.X, vector1.Y - vector2.Y);
        }

        /// <summary>
        /// Operator overload to allow for negation of vectors
        /// </summary>
        /// <param name="vector1">Vector2 to add</param>
        /// <returns>Resulting vector2 (-x1, -y1)</returns>
        public static TE_Vector2 operator -(TE_Vector2 vector1)
        {
            return new TE_Vector2(-vector1.X, -vector1.Y);
        }

        /// <summary>
        /// Rotates a Vector2
        /// </summary>
        /// <param name="angle">Angle to rotate</param>
        /// <returns>Rotated vector</returns>
        public TE_Vector2 Rotate(double angle)
        {
            return new TE_Vector2(Angle + angle, (float) Length);
        }

        /// <summary>
        /// Adds an offset to a vector2
        /// </summary>
        /// <param name="x">Offset x</param>
        /// <param name="y">Offset</param>
        /// <returns></returns>
        public TE_Vector2 Offset(float x, float y)
        {
            return new TE_Vector2(X + x, Y + y);
        }

        /// <summary>
        /// Adds an offset to a vector2 (modifies the vector)
        /// </summary>
        /// <param name="x"></param>
        /// <param name="y"></param>
        public void AddOffset(float x, float y)
        {
            X += x;
            Y += y;
        }

        /// <summary>
        /// Adds an offset to a vector2 (modifies the vector)
        /// </summary>
        public void AddOffset(TE_Vector2 offset)
        {
            AddOffset(offset.X, offset.Y);
        }

        public override string ToString()
        {
            return string.Format("[{0}, {1}]", X, Y);
        }

        public bool IsZero()
        {
            return X == 0 && Y == 0;
        }


        public TE_Vector2 ToUnit()
        {
            return new TE_Vector2((float) (X / Length), (float)(Y / Length));
        }

        public Vector2u ToVector2u()
        {
            return new Vector2u((uint) X, (uint) Y);
        }
    }
}